package gmu.swe.project.registry;

import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.Iterator;

import gmu.swe.project.io.ConfigFileParser;
import gmu.swe.project.tracker.TrackerInfo;
import gmu.swe.project.utils.Helper;

/**
 * Server that listens for tracker registrations and keep-alive messages on the
 * network When the registry server is started, it always checks for the other
 * registry on the network to retrieve the latest tracker registration set. If
 * none is found, it will presume to start the registry with a blank tracker
 * registration set. To start the server, the following arguments are needed: 1)
 * registry server id 2) socket number to start the registry listener on 3)
 * interval time frame for the registry to check the tracker registration
 * 
 * In addition, a -DCONFIG_FILE_LOCATION java option must be provided to
 * indicate where the config.xml file is to find the registry IPs/ports.
 * 
 * @author Stacy Meyer
 * 
 */
public class RegistryServer extends Thread {

	// create a TrackerRegistration to hold the info for each registered tracker
	// in the network
	private static TrackerRegistration mainTr = new TrackerRegistration();
	private static ArrayList<RegistryInfo> registryServers = null;
	private static int socketNum = 0;
	private static String socketNumString = "";
	private static String registryId = "";
	private static String intervalString = "";
	private static RegistryInfo otherRegistry = null;
	final private static String messageObj = "GET_TRACKER";
	private static String config = "";
	private static RegistryUI rUI;

	RegistryServer(String rId, String sockNum, String interval, RegistryUI ui,
			String config) {
		registryId = rId;
		socketNumString = sockNum;
		intervalString = interval;
		rUI = ui;
		this.config = config;
	}

	/**
	 * Two threads for the Registry Server, one to handle tracker registration
	 * and updates, another one to handle tracker keep alive messages. The keep
	 * alive thread is a single thread that gets started and run on a schedule.
	 * Part of the command line argument is to provide the schedule interval for
	 * the keep alive service thread. The registry service thread listens for
	 * incoming tracker requests and spawns off a new thread for each request.
	 * 
	 * @param args
	 */
	public void run() {

		/* Variable initializations */
		// socket number to start the listener
		socketNum = Integer.parseInt(socketNumString);

		// scheduled interval to check the tracker registration set
		int interval = Integer.parseInt(intervalString);
		boolean listening = true;
		// temporary, take out later
		int counter = 0;

		// Upon intial start up of a registry, go and find the other registry
		// grab its latet tracker registration and work off of that set. Else,
		// start from scratch
		readConfigFile();
		findOtherRegistry();
		getOtherRegistryTrackerRegistration();

		rUI.print("Registry Server :: start keepAlive thread");
		new KeepAliveServiceThread(interval).start();

		try {
			ServerSocket serverSocket = new ServerSocket(socketNum);
			while (listening) {
				rUI.print("RegistryServer::Counter: "
						+ Integer.toString(counter++));

				new RegistryServiceThread(serverSocket.accept(), rUI).start();
			}
			serverSocket.close();
			rUI.print("Registry server shut down");
		} catch (Exception e) {
			rUI.print(e.toString());
		}

	}// main

	/**
	 * Updates the trackerRegistration whenever a tracker sends an update or
	 * registers for the first time
	 */
	public static synchronized void updateTrackerRegistration(TrackerInfo ti) {
		rUI.print("RegistryServer::updateTrackerRegistration():update tracker");
		ti.setTimeStamp(Helper.getCurrTime());
		mainTr.updateTracker(ti);
		rUI.print("RegistryServer::updateTracker():trackerRegistration size: "
				+ Integer.toString(mainTr.getTrackerSet().size()));
	}// updateTrackerRegistration

	/**
	 * Returns all available trackers who have continuously been sending alive
	 * messages
	 * 
	 * @return
	 */
	public static TrackerRegistration getTrackerRegistration() {
		return mainTr;
	} // getAvailableTrackers

	/**
	 * Sets the current mainTr to the passed in tracker registration
	 * 
	 * @param tr
	 */
	public static void setTrackerRegistration(TrackerRegistration tr) {
		rUI
				.print("RegistryServer::setTrackerRegistration():reset the tracker registration");
		mainTr = tr;
	} // setTrackerRegistration

	/**
	 * Read the config file to get the ips and ports of current registries that
	 * are out there
	 */
	private static void readConfigFile() {

		// read the config file
		ConfigFileParser parser = new ConfigFileParser(config);

		// get the IP addresses/ports of the Registry servers
		registryServers = parser.getRegistryServers();

	}// readConfigFile

	/**
	 * Find the other registry by searching the registry list, but making sure
	 * it doesn't return its own connectivity information
	 * 
	 */
	private static void findOtherRegistry() {

		if (registryServers != null) {
			Iterator<RegistryInfo> riIter = registryServers.iterator();

			while (riIter.hasNext()) {
				RegistryInfo tmp = (RegistryInfo) riIter.next();
				// System.out
				// .println("RegistryServer::findOtherRegistry():tmp id: "
				// + tmp.getId());
				// System.out
				// .println("RegistryServer::findOtherRegistry():this id: "
				// + registryId);
				if (!tmp.getId().equalsIgnoreCase(registryId)) {
					otherRegistry = tmp;
					break;
				}
			}
		}
	} // findOtherRegistry

	/**
	 * Get the other registry's tracker registration and make a copy of that to
	 * the tracker registration for this registry
	 */
	private static void getOtherRegistryTrackerRegistration() {
		try {
			rUI
					.print("RegistryServer::getOtherRegistryTrackerRegistration(): otherRegistry IP: "
							+ otherRegistry.getIpAddress()
							+ " otherRegistry port: "
							+ Integer.toString(otherRegistry.getPort()));
			rUI
					.print("RegistryServer::getOtherRegistryTrackerRegistration(): Attempt to get tracker "
							+ "registration from other tracker");
			Socket socketConnection = new Socket(otherRegistry.getIpAddress(),
					otherRegistry.getPort());
			// output stream to write to the server
			ObjectOutputStream clientOutputStream = new ObjectOutputStream(
					socketConnection.getOutputStream());

			// incoming stream to receive from the server
			ObjectInputStream clientInputStream = new ObjectInputStream(
					socketConnection.getInputStream());

			clientOutputStream.writeObject(messageObj);

			TrackerRegistration tr = (TrackerRegistration) clientInputStream
					.readObject();

			rUI
					.print("RegistryServer::getOtherRegistryTracker()::trackerRegistration from other server, size: "
							+ Integer.toString(tr.getTrackerSet().size()));
			mainTr = tr;
			rUI
					.print("RegistryServer::getOtherRegistryTracker()::setup our main tracker registration");
			// close the streams
			clientOutputStream.close();
			clientInputStream.close();
			socketConnection.close();
		} catch (Exception ex) {
			rUI
					.print("RegistryServer::getOtherRegistryTrackerRegistration():ex: "
							+ ex.toString());
		}
	}// getOtherRegistryTrackerRegistration

}// RegistryServer
